package com.tiancheng.trade.payment.channel.cmb;

import com.alibaba.fastjson2.JSONObject;
import com.tiancheng.commom.pay.PayOrderAutoQueryConfig;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.exception.BusinessException;
import com.tiancheng.trade.commom.core.exception.error.BusinessErrorInfoEnum;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import com.tiancheng.trade.commom.web.feign.MerchantFeignService;
import com.tiancheng.trade.commom.web.http.PlatformRequest;
import com.tiancheng.trade.commom.core.utils.DateTimeUtil;
import com.tiancheng.trade.payment.channel.AbstractPayChannel;
import com.tiancheng.trade.payment.channel.bo.*;
import com.tiancheng.trade.payment.channel.cmb.params.*;
import com.tiancheng.trade.payment.channel.cmb.util.CmbSignUtil;
import com.tiancheng.trade.payment.channel.enums.ChannelOrderStatusEnum;
import com.tiancheng.trade.commom.core.enums.PaymentChannelEnum;
import com.tiancheng.trade.commom.core.enums.PaymentWayEnum;
import com.tiancheng.trade.commom.web.model.request.MerchantChannelRequestDTO;
import com.tiancheng.trade.commom.web.model.response.MerchantChannelResponseDTO;
import com.tiancheng.trade.payment.model.PaymentFlowDetailSettle;
import com.tiancheng.trade.payment.model.PaymentFlowDetailSettleDetail;
import com.tiancheng.trade.payment.util.*;
import com.tiancheng.trade.payment.util.cache.PaymentCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpPost;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 招总行支付渠道
 *
 * @author kellen
 * @date 2024/4/15
 */
@Slf4j
@Service
public class CmbPayChannel implements AbstractPayChannel {

    @Value(value = "${channel.cmb.appId}")
    private String appId;
    @Value(value = "${channel.cmb.payCallback}")
    private String payCallback;
    @Value(value = "${channel.cmb.domain}")
    private String channelUrl;
    @Value(value = "${channel.cmb.wxAppId}")
    private String wxAppId;

    @Resource
    private MerchantFeignService merchantFeignService;
    @Resource
    private JacksonJsonUtil jacksonJsonUtil;
    @Resource
    private CmbSignUtil cmbSignUtil;
    @Resource
    private PlatformRequest platformRequest;

    @Override
    public PaymentChannelEnum getPaymentChannelEnum() {
        return PaymentChannelEnum.CMB;
    }

    @Override
    public Result<UnifiedOrderResBO> unifiedOrder(UnifiedOrderBO unifiedOrderBO) throws BusinessException {
        // TODO 关闭招行通道支付能力，如后续有需求再开放
        BusinessErrorInfoEnum.CHANNEL_CLOSE.assertFail();

        // 调用商户中心，查询商户的渠道进件编号
        Map<String, String> merchantMap = new HashMap<>();
        unifiedOrderBO.getSubOrders().forEach(subOrderBO -> merchantMap.put(subOrderBO.getMerchantCode(),
                PaymentChannelEnum.CMB.getCode()));
        merchantMap.put(unifiedOrderBO.getMerchantCode(), PaymentChannelEnum.CMB.getCode());    //主商户
        List<MerchantChannelRequestDTO> merchantChannelRequestDTOS = merchantMap.entrySet().stream().map(entry ->
                new MerchantChannelRequestDTO(entry.getKey(), entry.getValue(),unifiedOrderBO.getOrgPaymentChannelId(),unifiedOrderBO.getBusinessCode())).collect(Collectors.toList());

        Result<List<MerchantChannelResponseDTO>> merchantChannelResult = merchantFeignService.findSubAppidList(merchantChannelRequestDTOS);
        BusinessErrorInfoEnum.CHANNEL_PAY_NO_MERCHANT.assertIsTrue(
                merchantChannelResult.getRet() == 0 && merchantChannelResult.getData().size() == merchantMap.size());
        merchantChannelResult.getData().forEach(r -> merchantMap.put(r.getMerchantCode(), r.getSubAppid()));
        merchantChannelResult.getData().forEach(r -> {
            if(!StringUtils.isEmpty(r.getMerchantCodeBusiness())) {
                merchantMap.put(r.getMerchantCodeBusiness(), r.getSubAppid());
            }
        });

        // 支付方式转换
        String payWay = unifiedOrderBO.getPayWay();
        if (PaymentWayEnum.ALIPAY.getCode().equals(payWay)) {
            payWay = "alipay_native";
        }

        CmbUnifiedOrderParam param = new CmbUnifiedOrderParam();
        param.setAppid(appId);
        param.setUserId(unifiedOrderBO.getUid());
        param.setOutTradeNo(unifiedOrderBO.getOrderId());
        param.setProductId(unifiedOrderBO.getSubOrders().get(0).getProductCode());
        param.setProductName(CommonUtil.trim(CommonUtil.subStr(unifiedOrderBO.getOrderName(), 125, 40)));
        param.setProductDetail(CommonUtil.trim(CommonUtil.subStr(unifiedOrderBO.getOrderName(), 125, 40)));
        param.setCurrencyType("CNY");
        param.setAmount(unifiedOrderBO.getOrderAmount());   // 计平这个坑货，实付金额也要传原始金额，带优惠券他们基于这个金额算
        param.setTs(TsTimeUtil.getUTCTimeStr());
        param.setChannel(payWay);
        param.setRealChannel("openbank_cmb");       //设置招总行
        param.setMetadata(unifiedOrderBO.getMetadata());
        param.setNum(unifiedOrderBO.getSubOrders().stream().mapToInt(UnifiedOrderBO.SubOrderBO::getProductCount).sum());
        param.setCallbackUrl(payCallback);
        param.setPlatformIncome(unifiedOrderBO.getOrderAmount());
        param.setBussSettleAmount(0);
        param.setOriginalAmount(unifiedOrderBO.getOrderAmount());
        param.setWxOpenid(unifiedOrderBO.getOpenid());
        param.setWxSubOpenid(unifiedOrderBO.getOpenid());
        param.setNeedSettleCheck(1);
        param.setSpInfo(unifiedOrderBO.getSpInfo());
        param.setSettleAppid(merchantMap.get(unifiedOrderBO.getMerchantCode()));
        // 如果是小程序支付，subAppId使用配置的
        if(!StringUtils.isEmpty(unifiedOrderBO.getOpenid())) {
            param.setSubAppid(wxAppId);
        } else {
            param.setSubAppid(merchantMap.get(unifiedOrderBO.getMerchantCode()));
        }
        if (PaymentWayEnum.WECHAT.getCode().equals(payWay)) {
            param.setGoodsTag(unifiedOrderBO.getGoodsTag());
        }

        List<CmbUnifiedOrderParam.SubOutTradeNo> subOutTradeNos = new ArrayList<>();
        unifiedOrderBO.getSubOrders().forEach(subOrderBO -> {
            CmbUnifiedOrderParam.SubOutTradeNo subOutTradeNo = new CmbUnifiedOrderParam.SubOutTradeNo();
            subOutTradeNo.setSubOutTradeNo(String.valueOf(subOrderBO.getSubOrderId()));
            subOutTradeNo.setAmount(subOrderBO.getOrderAmount());
            subOutTradeNo.setProductId(subOrderBO.getProductCode());
            subOutTradeNo.setProductName(CommonUtil.trim(CommonUtil.subStr(subOrderBO.getProductName(), 125, 40)));
            subOutTradeNo.setProductDetail(CommonUtil.trim(CommonUtil.subStr(subOrderBO.getProductDetail(), 125, 40)));
            subOutTradeNo.setPlatformIncome(subOrderBO.getOrderAmount());
            subOutTradeNo.setBussSettleAmount(0);
            subOutTradeNo.setOriginalAmount(subOrderBO.getOrderAmount());
            subOutTradeNo.setSubAppid(merchantMap.get(subOrderBO.getMerchantCode()));
            subOutTradeNo.setNeedSettleCheck(param.getNeedSettleCheck());
            subOutTradeNos.add(subOutTradeNo);
        });
        param.setSubOutTradeNoList(JsonUtil.toJsonString(subOutTradeNos));

        // 设置透传数据
        String metadata = unifiedOrderBO.getMetadata();
        Map<String, String> metadataMap = StringUtils.isEmpty(metadata) ? new HashMap<>() : jacksonJsonUtil.fromJson(metadata, HashMap.class);
        metadataMap.put("system_source", "trade");
        param.setMetadata(JsonUtil.toJsonString(metadataMap));

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/unified_order?" + "sign=" + sign;
        log.info("cmb_unifiedOrder_url:{}, params:{}", url, paramMap);

        Result<UnifiedOrderResBO> result = new Result<>();
        try {
            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,param,false,null);
            log.info("招总行计平统一下单返回:{}", payResultJsonStr);
            result = jacksonJsonUtil.fromJson(payResultJsonStr, Result.class);
            result.setData(jacksonJsonUtil.fromJson(payResultJsonStr, UnifiedOrderResBO.class));
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("下单支付失败");
        }
        return result;
    }

    @Override
    public Result<GetUnifiedOrderResBO> get(GetUnifiedOrderBO getUnifiedOrderBO) throws BusinessException {
        CmbGetUnifiedOrderParam param = new CmbGetUnifiedOrderParam();
        param.setAppid(appId);
        param.setUserId(getUnifiedOrderBO.getUid());
        param.setType("by_order");
        param.setTs(TsTimeUtil.getUTCTimeStr());
        param.setOutTradeNo(getUnifiedOrderBO.getOrderId());
        param.setTransactionId(getUnifiedOrderBO.getTransactionId());

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/query_order?sign=" + sign;
        log.info("cmb_get_url:{}, params:{}", url, paramMap);

        Result<GetUnifiedOrderResBO> result = new Result<>();
        result.setRet(0);
        try {
            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,paramMap,false,null);
            Map<String, Object> getResult = JsonUtil.fromJson(payResultJsonStr, Map.class);
            log.info("招总行计平查询订单返回:{}", payResultJsonStr);

            if("0".equals(String.valueOf(getResult.get("ret"))) && Integer.valueOf(String.valueOf(getResult.get("total_num"))) > 0) {
                List<Map<String, Object>> orderList = (List<Map<String, Object>>) getResult.get("order_list");
                Map<String, Object> orderMap = orderList.get(0);

                GetUnifiedOrderResBO resBO = new GetUnifiedOrderResBO();
                resBO.setOrderId(String.valueOf(orderMap.get("out_trade_no")));
                resBO.setTransactionId(String.valueOf(orderMap.get("transaction_id")));
                resBO.setAmount(Integer.valueOf(String.valueOf(orderMap.get("amount"))));
                String payTime = String.valueOf(orderMap.get("pay_time"));
                if(!"0000-00-00 00:00:00".equals(payTime)) {
                    resBO.setPayTime(DateTimeUtil.string2LocalDateTime(payTime, "yyyy-MM-dd HH:mm:ss"));
                }

                String orderStatus = String.valueOf(orderMap.get("order_state"));
                if("0".equals(orderStatus) || "1".equals(orderStatus)) {
                    resBO.setOrderStatus(ChannelOrderStatusEnum.wait);
                } else if ("2".equals(orderStatus) || "3".equals(orderStatus) || "4".equals(orderStatus)) {
                    resBO.setOrderStatus(ChannelOrderStatusEnum.success);
                } else {
                    resBO.setOrderStatus(ChannelOrderStatusEnum.fail);
                }
                result.setData(resBO);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("查询订单失败");
        }
        return result;
    }

    @Override
    public Result<RefundOrderResBO> refund(RefundOrderBO refundOrderBO) throws BusinessException {
        CmbRefundOrderParam param = new CmbRefundOrderParam();
        param.setAppid(appId);
        param.setUserId(refundOrderBO.getUid());
        param.setRefundId(refundOrderBO.getRefundId());
        param.setBussSettleAmount(0);
        param.setOutTradeNo(refundOrderBO.getOrderId());
        param.setTs(TsTimeUtil.getUTCTimeStr());
        // 计平不能传实际支付金额，要带上优惠金额，计平自己要扣除这部分
        int refundAmount = refundOrderBO.getAmount() + (refundOrderBO.getDiscount() != null ? refundOrderBO.getDiscount() : 0);
        param.setAmount(refundAmount);
        param.setPlatformIncome(refundAmount);

        List<CmbRefundOrderParam.SubOutTradeNo> subOutTradeNos = new ArrayList<>();
        refundOrderBO.getSubOrders().forEach(subOrderBO -> {
            CmbRefundOrderParam.SubOutTradeNo subOutTradeNo = new CmbRefundOrderParam.SubOutTradeNo();
            // 子订单的退款金额一样，同上
            int subRefundAmount = subOrderBO.getAmount() + (subOrderBO.getDiscount() != null ? subOrderBO.getDiscount() : 0);
            subOutTradeNo.setRefundAmt(subRefundAmount);
            subOutTradeNo.setPlatformRefundAmt(subRefundAmount);
            subOutTradeNo.setBussSettleRefundAmt(0);
            subOutTradeNo.setSubOutTradeNo(subOrderBO.getSubOrderId());
            subOutTradeNos.add(subOutTradeNo);
        });
        final JSONObject subOutTradeNoList = new JSONObject();
        subOutTradeNoList.put("suborder_list", JsonUtil.toJsonString(subOutTradeNos));


        param.setSubOutTradeNoList(subOutTradeNoList.toString());

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/refund?" + "sign=" + sign;
        log.info("cmb_refund_url:{}, params:{}", url, paramMap);

        Result<RefundOrderResBO> result = new Result<>();
        try {
            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,paramMap,false,null);

            log.info("招总行计平退款返回:{}", payResultJsonStr);
            Map<String, Object> refundResult = JsonUtil.fromJson(payResultJsonStr, Map.class);

            // 退款时，计平调用招行超时（计平会重试，通过回调确认退款）
            if("4-4003-0".equals(String.valueOf(refundResult.get("err_code")))) {
                // 缓存该笔退款超时流水，等待计平回调错误退款结果时重新退款
                result.setRet(0);
                result.setErrorCode(0);
                PaymentCache.setRefundTimeoutFlow(refundOrderBO.getRefundId(),refundOrderBO);
                return result;
            }
            // 退款时，如果风控，则放入延迟队列重试
            result = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(refundResult), Result.class);
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("退款失败");
        }
        return result;
    }

    @Override
    public Result<GetRefundOrderResBO> getRefund(GetRefundOrderBO getRefundOrderBO) throws BusinessException {
        CmbGetRefundOrderParam param = new CmbGetRefundOrderParam();
        param.setAppid(appId);
        param.setUserId(getRefundOrderBO.getUid());
        param.setRefundId(getRefundOrderBO.getRefundId());
        param.setTs(TsTimeUtil.getUTCTimeStr());

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/query_refund?" + "sign=" + sign;
        log.info("cmb_getRefund_url:{}", url);

        Result<GetRefundOrderResBO> result = new Result<>();
        try {

            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,paramMap,false,null);
            Map<String, Object> getResult = JsonUtil.fromJson(payResultJsonStr, Map.class);
            log.info("招总行计平退款查询返回:{}", getResult);

            result = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(getResult), Result.class);
            if(result.getRet() == 0) {
                GetRefundOrderResBO resBO = new GetRefundOrderResBO();
                switch (Integer.valueOf(String.valueOf(getResult.get("state"))).intValue()) {
                    case 1: resBO.setChannelOrderStatusEnum(ChannelOrderStatusEnum.ing); break;
                    case 2: resBO.setChannelOrderStatusEnum(ChannelOrderStatusEnum.success); break;
                    case 3: resBO.setChannelOrderStatusEnum(ChannelOrderStatusEnum.fail); break;
                }
                result.setData(resBO);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("查询退款失败");
        }
        return result;
    }

    @Override
    public Result close(CloseOrderBO closeOrderBO) throws BusinessException {
        CmbCloseOrderParam param = new CmbCloseOrderParam();
        param.setAppid(appId);
        param.setUserId(closeOrderBO.getUid());
        param.setTransactionId(closeOrderBO.getTransactionId());
        param.setOutTradeNo(closeOrderBO.getOrderId());
        param.setTs(TsTimeUtil.getUTCTimeStr());

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/close_order?" + "sign=" + sign;
        log.info("cmb_close_url:{}, params:{}", url, paramMap);

        Result result = new Result();
        try {

            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,paramMap,false,null);
            Map<String, Object> closeResult = JsonUtil.fromJson(payResultJsonStr, Map.class);
            log.info("招总行计平关闭订单返回:{}", closeResult);

            // 关单会有一个特殊错误码，ret=1003且err_code=“1003-30000-31002”，这种错误可认为关单正常，忽略即可
            if("1003".equals(String.valueOf(closeResult.get("ret"))) && "1003-30000-31002".equals(String.valueOf(closeResult.get("err_code")))) {
                return Result.success();
            } else {
                result = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(closeResult), Result.class);
            }
            return result;
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("关闭订单失败");
        }
        return result;
    }


    @Override
    public PayResultBO payResult(Map<String, Object> payResult) {
        // todo 验证sign

        PayResultBO payResultBO = new PayResultBO();
        payResultBO.setStatus("success");
        payResultBO.setUid(String.valueOf(payResult.get("user_id")));
        payResultBO.setOrderId(String.valueOf(payResult.get("out_trade_no")));
        payResultBO.setTradeNo(String.valueOf(payResult.get("pay_channel_orderid")));
        payResultBO.setAmount(Integer.valueOf(String.valueOf(payResult.get("amount"))));
        payResultBO.setMetadata(String.valueOf(payResult.get("metadata")));
        // TODO 根据实际情况修改优惠金额
        payResultBO.setCouponAmount(0);
        payResultBO.setPayTime(LocalDateTime.now());
        return payResultBO;
    }

    @Override
    public RefundResultBO refundResult(Map<String, Object> refundResult) {
        // todo 验证sign

        try {
            RefundResultBO refundResultBO = new RefundResultBO();
            refundResultBO.setStatus("refund_suc".equals(String.valueOf(refundResult.get("SubCmdCode"))) ? "success" : "fail");

            String portalExtendValue = URLDecoder.decode(String.valueOf(refundResult.get("PortalExtendField")), "utf-8");
            Map<String, Object> portalExtendMap = CommonUtil.parseParams(portalExtendValue);
            refundResultBO.setRefundId(String.valueOf(portalExtendMap.get("refund_id")).split("_")[1]);
            refundResultBO.setTransactionId(String.valueOf(portalExtendMap.get("refund_id")));

            if("success".equals(refundResultBO.getStatus())) {
                if(refundResult.containsKey("ChannelRefundId")) {
                    refundResultBO.setTradeNo(String.valueOf(refundResult.get("ChannelRefundId")));
                }
                refundResultBO.setAmount(Integer.valueOf(String.valueOf(portalExtendMap.get("refund_amt"))));
                // TODO 如果计平通道也有微信代金券的话，需要处理amount、refundAmount字段
                refundResultBO.setRefundAmount(refundResultBO.getAmount());
                refundResultBO.setRefundTime(LocalDateTime.now());
                Optional<RefundOrderBO> optional = PaymentCache.getRefundTimeoutFlow(refundResultBO.getRefundId());
                if(optional.isPresent()){
                    // 清空缓存的超时退款流水信息
                    PaymentCache.delRefundTimeoutFlow(refundResultBO.getRefundId());
                }
            } else {
                // 退款回调失败时，根据缓存的超时退款单判断是否需要重新发起退款
                Optional<RefundOrderBO> optional = PaymentCache.getRefundTimeoutFlow(refundResultBO.getRefundId());
                if(optional.isPresent()){
                    // 清空缓存的超时退款流水信息
                    this.refund(optional.get());
                    throw new BusinessException(BusinessErrorInfoEnum.CHANNEL_REFUND_TIMEOUT_FLOW_ERROR, null, "计平退款超时，重新进行状态同步");
                }
            }
            return refundResultBO;
        } catch (Exception e) {
            throw new BusinessException(BusinessErrorInfoEnum.CHANNEL_CALLBACK_REFUND_ERROR, null, e.getMessage(), e);
        }
    }

    @Override
    public Result settleV2(List<PaymentFlowDetailSettle> detailSettles, List<PaymentFlowDetailSettleDetail> detailSettlesDetails) throws BusinessException {
        /*CmbSettleOrderParam param = new CmbSettleOrderParam();
        param.setAppid(appId);
        param.setUserId(settleOrderBO.getUid());
        param.setAmount(settleOrderBO.getAmount());
        param.setOutTradeNo(settleOrderBO.getFlowId());
        param.setSubOutTradeNo(settleOrderBO.getSubOrderId());
        param.setVerificationId(settleOrderBO.getVerificationId());
        param.setTs(TsTimeUtil.getUTCTimeStr());

        SortedMap<String, Object> paramMap = jacksonJsonUtil.fromJson(JsonUtil.toJsonString(param), TreeMap.class);
        String sign = cmbSignUtil.sign(paramMap);

        String url = channelUrl + "/verification?" + "sign=" + sign;
        log.info("cmb_settle_url:{}, params:{}", url, paramMap);

        Result result = new Result();
        try {
            String payResultJsonStr = platformRequest.extendRequest(url, HttpPost.METHOD_NAME,null,paramMap,false,null);
            final Result result1 = JsonUtil.fromJson(payResultJsonStr, Result.class);
            log.info("招总行计平核销订单返回:{}", result);
            return result;
        } catch (Exception e) {
            log.error(e.getMessage());
            result.setRet(1);
            result.setMsg("核销订单失败");
        }
        return result;*/
        return Result.success();
    }

    @Override
    public boolean autoQueryStatus(Long orgPaymentChannelId) {
        return false;
    }

    @Override
    public PayOrderAutoQueryConfig getOrderAutoQueryConfig(Long orgPaymentChannelId) {
        return null;
    }

}
